package com.ruiysoft.pub.lock;

import com.alibaba.fastjson.JSON;
import com.ruiysoft.pub.executor.AfterCommitExecutor;
import com.ruiysoft.util.Md5Util;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Optional;

@Aspect
@Component(value = "lockAspect")
public class LockAspect {
    private Logger logger = LoggerFactory.getLogger(LockAspect.class);
    private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
    private final RedisLock redisLock;
    private final AfterCommitExecutor afterCommitExecutor;
    @org.springframework.beans.factory.annotation.Autowired
    public LockAspect(RedisLock redisLock,AfterCommitExecutor afterCommitExecutor) {
        this.redisLock = redisLock;
        this.afterCommitExecutor = afterCommitExecutor;
    }

    @Pointcut("@annotation(com.ruiysoft.pub.lock.Lock)")
    public void Lock(){    }
    @Around("Lock()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        String identifier = null;
        MethodSignature signature = (MethodSignature) pjp.getSignature();

        Method method = signature.getMethod();
        String[] paramNames = signature.getParameterNames();
        Object[] args = pjp.getArgs();

        Lock anno = method.getAnnotation(Lock.class);
        String key = anno.key();
        String id = anno.id();
        long expire = anno.expire();
        long acquireTimeout = anno.acquireTimeout();
        String message = anno.message();
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < paramNames.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        key = key.startsWith("#")
                ? Optional.ofNullable(EXPRESSION_PARSER.parseExpression(key).getValue(context))
                .map(v -> Md5Util.MD5(JSON.toJSONString(v)))
                .orElse(key)
                : key;
        id = id.startsWith("#")
                ? Optional.ofNullable(EXPRESSION_PARSER.parseExpression(id).getValue(context, String.class))
                .orElse(id)
                : id;
        String cacheKey = key+id;
        try {

            identifier = redisLock.lockWithTimeout(cacheKey, acquireTimeout, expire);
            if(StringUtils.isBlank(identifier)){
                logger.info("{} - redisLock锁获取失败，参数为--{}","cacheKey:"+cacheKey);
                throw new RuntimeException(message);
            }
            Object object =  pjp.proceed();
            String finalIdentifier = identifier;

            afterCommitExecutor.execute(() -> redisLock.releaseLock(cacheKey, finalIdentifier));
            return object;
        } catch (Throwable e) {
            redisLock.releaseLock(cacheKey, identifier);
            throw e;
        }
    }

}
